package jdesktop.appbar.win32;

import com.sun.jna.Native;
import com.sun.jna.Pointer;
import jdesktop.appbar.*;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;
import java.util.logging.Logger;

/**
 * <h3 id="auto-hiding-insets">Auto-hideing insets</h2>
 * Insets can be hidden or shown automatically based what is adjacent to them.
 * An auto-hidden inset is hidden when it is adjacent to the edge of the screen, and
 * shwn when it is next to another toolbar.vtzsj b,m6afrbhjuggow . a toolbar
 * on the left sie
 *
 * todo - autohide, draggable sizing edges, draggable move client area
 * todo - size, is it set explicitly or does it come from the layout?
 * todo - hide non client area
 * todo - use different layout when docked? Or have layout use "isDocked" property to determine appropriate layout?
 * todo - keyboard equivalents for dragging/resizing/undocking
 * 
 *  or does docking function by setting an explicit size?
 *
 * client area has no insets at all
 * reserved area contains the full size of visible insets, and the
 * revealSize of hidden insets.
 * the window area contains the size of the full inssets.
 * the clip region may be used to clip-non-visible parts of hidden insets
 *
 * the window area is the client area plug all insets.
 *
 * todo - split up positioning code to decouple from Shell, for easier unit testing.
 *  (e.g. should be possible to mock all Win32 calls, get pointer, get window rect etc.)
 * This will also make it simpler to port to other platforms.
 */
public class WAppBar extends AppBar implements WindowMessageHandler, AppBarModel, DesktopEnvironment
{
    public final Logger log = Logger.getLogger(getClass().getName());

    /*
     * Properties documentation. Ideally these fields would be of type Property or some other structured
     * non-reflective representation. For now, we use property names. The main point here is
     * to document the property in one place, and not on each getter/setter method.
     */

    /**
     * The edge that the app bar occupies. When positioned along an edge, the primary axis of the
     * appbar occupies as much of the screen along that axis as possible. E.g. if the app bar is
     * horizontal, then it will occupy as much of the width of the screen as possible.
     * The default value is the first edge that is allowed by the {@link #ALLOWEDEDGES} property.
     * @see #setEdge(jdesktop.appbar.Edge)
     * @see #getEdge()
     */
    public static final String EDGE = AppBarModel.EDGE;

    /**
     * A {@link jdesktop.appbar.Sides} instance that defines the edges that this appbar is allowed to
     * occupy. If the appbar can be dragged, it can only be dragged to an allowed edge.
     * By default, all edges are allowed.
     * @see #onChangedAllowedEdges()
     */
    public static final String ALLOWEDEDGES = AppBarModel.ALLOWEDEDGES;


    /**
     * A {@link jdesktop.appbar.Sides} instance that defines which insets are hidden,
     * when the corresponding value in {@link #AUTOHIDEINSETS} is {@code false}.
     * No insets are set for manual hidden by default (all values {@code false}.)
     * @see #onChangedOverrideHideInsets
     * @see #getOverrideHideInsets
     */
    public static final String OVERRIDEHIDEINSETS = "overrideHideInsets";

    /**
     * A {@link jdesktop.appbar.Sides} instance that defines which insets are set automatically, and
     * which are set from {@link #OVERRIDEHIDEINSETS}. When a side returns {@code false},
     * the hidden state for that inset is taken from the corresponding side of
     * {@link #OVERRIDEHIDEINSETS}. When a side returns {@link true} the inset for that side
     * is hidden or shown automatically.
     * By default, all insets are auto-hidden.
     * @see #onChangedAutoHideInsets
     * @see #getAutoHideInsets
     */
    public static final String AUTOHIDEINSETS = "autoHideInsets";

    /**
     * The insets that will be autohidden, if {@link #AUTOHIDEINSETS autoHideInsets} is
     * set for corresponding edge.
     * @see #getAutoHiddenInsets
     */
    public static final String AUTOHIDDENINSETS = "autoHiddenInsets";

    /**
     * The actual hide insets state.
     * @see #getInsetsHideState
     */
    static final public String INSETSHIDESTATE = "insetsHideState";

    /**
     * The window state can be restored to how it was before registering with the appbar
     * when the appbar is unregistered. When {@code true}, the window state (size, visibility,
     * and window styles) are stored at registration time, and restored when the window
     * is unregistered.
     * Default value is {@vale #DEFAULT_RESTORESTATE}.
     */
    public static final String RESTORESTATE = "restoreState";
    public static final boolean DEFAULT_RESTORESTATE = AppBarModel.DEFAULT_RESTORESTATE;



    /**
     * The current size of the appbar. This is the number of pixels from the inner edge to the outer edge
     * of the appbar's client area. It does not include the size of the insets. Hence, setting size to 0
     * produces a window where just the decoration is visible.
     * Default value is {@value #DEFAULT_BARSIZE}.
     * @see #getBarSize(), #setBarSize(int)
     */
    public static final String BARSIZE = AppBarModel.BARSIZE;
    public static final int DEFAULT_BARSIZE = 36;

    /**
     * The explicitly set, desired size of the toolbar.
     * @see #setDesiredBarSize(int)
     */
    public static final String DESIREDBARSIZE = AppBarModel.DESIREDBARSIZE;

    /**
     * The docked state of this appbar. When {@code true} the appbar is docked, reserving a portion
     * of the desktop, and offsetting all maximized windows. When {@code false} no desktop area is reserved, and
     * the window styles are returned to the valeus they were before the appbar was registered.
     * The default value is {@code true}.
     */
    public static final String DOCKED = AppBarModel.DOCKED;

    // implementation-specific properties

    // These capabilities describe what the user can do with the appbar.
    // They do not stop callers of the API from changing the appbar position. E.g.
    // If the appbar is not {@code draggable}, the user cannot drag it to a new edge,
    // but API callers can still use {@link #setEdge} to position the appbar on a new edge.
    // However, external changes, such as another process repositioning the window are not allowed,
    // and the appbar will reset itself based on the last set properties.

    /**
     * Can the window be dragged about to reposition? When {@code true}, the
     * window can be dragged to move to a different edge, or dock/undock (if {@link #FLOATABLE floatable}).
     * When {@code false}, the edge and float state can be changed programmatically, via the
     * {@link #EDGE edge} and {@link #DOCKED docked} properties.
     */
    public static final String DRAGGABLE = "draggable";

    /**
     * The instance of {@link AppBarEventHandler} that handles notifications from the appbar.
     * The default value is an instance of {@link AppBarEventHandlerSupport}.
     */
    public static final String APPBAREVENTSHANDLER = "appbarEventsHandler";


    /**
     * The window that is the displayable aspect of the appbar.
     */
    public static final String WINDOW = "window";

    /**
     * The registration state of this AppBar. An appbar is registered when
     * it is associated with a Window.
     *
     * @see #isRegistered()
     * @see #register(java.awt.Window)
     * @see #register(java.awt.Window, boolean)
     * @see #unregister()
     * @see #getWindow()
     */
    public static final String REGISTERED = "registered";

    /**
     * Read-only {@link Rectangle} property for the appBar area reserved for the appbar.
     * If the appbar is not {@linkplain #DOCKED docked}, or not {@linkplain #REGISTERED registered}, then the returned area is empty.
     */
    public static final String RESERVEDAREA = "reservedArea";

    /**
     * Local reference for convenience.
     * This will give early detection of any binding problems.
     */
    static final User32 user32 = User32.INSTANCE;
    static final Shell32 shell32 = Shell32.INSTANCE;

    // note that we duplicate most of the property state from the model.
    // This can be thought of as caching, but also allowing access from the window thread



    /**
     * The edgeCode that corresponds to the current edge.
     */
    private int edgeCode;

    /**
     * The window procedure used to subclass the window while it is an appbar.
     */
    private AppBarWndProc wndProc;

    /**
     * @see #WINDOW
     */
    private Window window;

    /**
     * The native window handle.
     */
    private Pointer hWnd;

    /**
     * Structure used to send messages to the shell.
     * @see #appBarMessage(int, AppBarData)
     */
    private final AppBarData appBarData = new AppBarData();

    /**
     * Internal state. Set to {@code true} when the Shell has been informed about the app bar.
     * Chnages in state are notified to the native component.
     * @see #appBarRegister()
     */
    private boolean appBarRegistered;

    /**
     * Internal state. Set to {@code true} when the appbar has allocated space on the screen
     * This happens when {@link #setPosition(java.awt.Rectangle, java.awt.Rectangle)} or one of
     * it's related methods is called. When space has been allocated, the window state and appbar
     * state are kept in sync.
     * todo - how is this different from docked?
     *
     * @see #setPositionFromModel()
     * @see #setPositionFromWindow()
     */
    private boolean spaceAllocated;

    /**
     * An auto-unregister window listener that disconnects the appbar from the window
     * when the window is closed.
     * @see #register(java.awt.Window, boolean)
     */
    private Unregister autoUnregisterListener;

    /**
     * @see #APPBAREVENTSHANDLER
     */
    private AppBarEventHandler appBarEventsHandler = new AppBarEventHandlerSupport(this);

    private boolean restoreWindowState = DEFAULT_RESTORESTATE;
    private final WindowStyle originalStyle = new WindowStyle();
    private boolean originalVisible;
    private Rectangle originalBounds;

    private final WindowStyle appBarStyle = new WindowStyle();

    /**
     * The styles for the appBar. Typically appbars are also topmost windows, but this
     * can be set on the window directly.
     *
     * @see Window#setAlwaysOnTop(boolean)
     */
    private static final int appBarExStyleFlagsAdd = User32.WS_EX_TOPMOST | User32.WS_EX_TOOLWINDOW | User32.WS_EX_WINDOWEDGE;
    private static final int appBarStyleFlagsAdd = User32.WS_POPUP | User32.WS_BORDER | User32.WS_THICKFRAME;
    private static final int appBarStyleFlagsRemove = User32.WS_OVERLAPPEDWINDOW;

    /**
     * The size of the most recent region set on the window.
     * {@code null} if no region is set.
     */
    private Rectangle lastRegion;

    /**
     * Enables hiding of insets by setting a region on the window.
     * @see #clipWindowInsets
     */
    private boolean regionClipEnabled = true;

    private final PropertyChangeSourceSupport propertyChangeSupport;

    /**
     * When {@link true} changes in the window size are immediately reflected in changes to the
     * reserved area for the appbar. When {@link false}, the reserved area remains unchanged
     * from the last setting. Although the reserved area is not set, it is queried, e.g. for
     * drag bounds tracking.
     */
    private int updateReservedAreaCount = 0;


    /**
     * The edges that this appbar can be docked to.
     * @see #ALLOWEDEDGES
     */
    private final Sides allowedEdges = new Sides().setAll(true);

    /**
     * The edge of the screen this appbar occupies. Default value is {@value}.
     * @see #EDGE
     */
    private Edge edge = Edge.LEFT;

    /**
     * The size of the bar. This is size of the dimension that is parallel to the orientation
     * of the bar. For a bar along the top/bottom edge, it is the heigh of the bar, for a bar
     * along the left/right edge, it is the width of the bar.)
     * <p>
     * This value can be set explicitly via {@link #setBarSize} or implicitly when the window
     * is resized.
     * @see #setBarSize
     * @see #getBarSize
     */
    private int barSize = DEFAULT_BARSIZE;

    /**
     * The last explicitly set barsize.
     * @see #barSize
     */
    private int desiredBarSize = DEFAULT_BARSIZE;

    private double autoHideAmount = 0.0;


    /**
     * Manually specified hidden state of insets.
     * @see #OVERRIDEHIDEINSETS
     */
    private final Sides overrideHideInsets = new Sides(true);

    /**
     * Defines which insets to auto-hide.
     * @see #AUTOHIDEINSETS
     */
    private final Sides autoHideInsets = new Sides(true);

    /**
     * The insets auto-hide state.
     */
    private final Sides autoHiddenInsets = new Sides(false);

    /**
     * The final state for whether or not an inset is hidden.
     */
    private final Sides insetsHideState = new Sides(false);

    /**
     * When an inset is hidden, normally the ensire inset is hidden. This property
     * allows part of the insets to be revealed. This mimics the taskbar in Windows, which
     * displays 2px of the inset. Naturally, this is very dependent upon the styling and
     * version of windows..
     * The default {@code insetReveal} value of zero for an inset hides
     * the entire inset when it is flagged as hidden.
     * A Positive value
     * reveals a corresponding number of pixels of the inset from the inside edge.
     * A negative value reveals from the outside edge. Setting a value whose magnitude is
     * greater than the corresponding inset reveals the entire inset.
     */
    private final Insets insetsReveal = new Insets(0,0,0,0);

    /**
     * The {@link #DOCKED} property value.
     */
    private boolean docked      = true;

    /**
     * The {@link #DRAGGABLE draggable} property value.
     */
    private boolean draggable   = true;

    /**
     * The {@link #RESIZABLE resiable} property.
     */
    private boolean resizable   = true;

    /**
     * The {@link #FLOATABLE floatable} property.
     */
    private boolean floatable = true;

    /**
     * The area into which the appbar will become floated.
     */
    private Rectangle floatableRectangle = null;

    /**
     * When floated, this is the bounds of the window.
     */
    private Rectangle floatBounds;

    /**
     * The last area reserved by the shell. Width and height will be 0 if no area is reserved.
     * @see #setPosition
     */
    private Rectangle   lastSetArea = new Rectangle();

    /**
     * The drag guide, used for dragging the appbar.
     * @see #getDragGuide()
     */
    private DragGuide dragGuide;

    /**
     * Maintains a counter for the number of times updateWindow has been called.
     *
     */
    private int updateWindow;

    /**
     * The location of the window when floating
     */
    private Point floatingLocation = new Point();
    private Dimension floatingSize = new Dimension();

    public WAppBar()
    {
        propertyChangeSupport = new PropertyChangeSourceSupport(this, true);
        internalAddListeners();
    }

    private void internalAddListeners()
    {
        allowedEdges.addPropertyChangeListener(new PropertyChangeListener()
        {
            public void propertyChange(PropertyChangeEvent evt)
            {
                onChangedAllowedEdges();
            }
        });

        overrideHideInsets.addPropertyChangeListener(new PropertyChangeListener()
        {
            public void propertyChange(PropertyChangeEvent evt)
            {
                onChangedOverrideHideInsets();
            }
        });

        autoHideInsets.addPropertyChangeListener(new PropertyChangeListener()
        {
            public void propertyChange(PropertyChangeEvent evt)
            {
                onChangedAutoHideInsets();
            }
        });
    }

    /**
     * Notification that the overrideHideInsets has changed.
     */
    protected void onChangedOverrideHideInsets()
    {
        refreshHiddenInsetState();
    }

    protected void onChangedAutoHideInsets()
    {
        refreshHiddenInsetState();
    }

    /**
     * Recomputes the new hidden state for all insets. If there is any change, the
     * appbar is repositioned.
     */
    protected void refreshHiddenInsetState()
    {
        Sides newState = new Sides().assign(insetsHideState);
        updateInsetsHiddenState(insetsHideState);
        if (!insetsHideState.equals(newState))
            windowPropertyChanged(INSETSHIDESTATE);
    }

    /**
     * Notification that the set of allowed edges has changed.
     * If the current edge is not in the set of allowed edges, it is set to the next edge
     * that is allowed, trying edges in a clockwise direction.
     * @throws 
     */
    protected void onChangedAllowedEdges()
    {
        Edge currentEdge = edge;
        for (int i=0; i<Edge.EDGE_COUNT; i++)
        {
            if (isEdgeAllowed(currentEdge))
                break;
            currentEdge = currentEdge.getAdjacentEdgeCW();
        }

        setEdge(currentEdge);
    }

    /**
     * Fetches the event handler for this bar.
     * @return The event handler previously set.
     * @see #APPBAREVENTSHANDLER
     */
    public AppBarEventHandler getAppBarEventsHandler()
    {
        return appBarEventsHandler;
    }

    /**
     * Sets the event handler for the appbar.
     * @param appBarEventsHandler    The event handler for appbar notifications.
     */
    public void setAppBarEventsHandler(AppBarEventHandler appBarEventsHandler)
    {
        AppBarEventHandler oldHandler = this.appBarEventsHandler;
        if (oldHandler!=appBarEventsHandler)
        {
            this.appBarEventsHandler = appBarEventsHandler;
            propertyChangeSupport.firePropertyChange(APPBAREVENTSHANDLER, oldHandler, appBarEventsHandler);
        }
    }

    /**
     * Converts an edge to an edgeCode used by the Shell API.
     * @param e     The edge to convert.
     * @return      One of the Shell32.ABE_xxx constants.
     * @throws      NullPointerException if {@code e} is null.
     * @throws      IllegalArgumentException if {@code e} is not
     * {@code LEFT}, {@code TOP}, {@code RIGHT} or {@code BOTTOM}.
     */
    public static int edgeCode(Edge e)
    {
        switch (e)
        {
            case LEFT: return Shell32.ABE_LEFT;
            case RIGHT: return Shell32.ABE_RIGHT;
            case TOP: return Shell32.ABE_TOP;
            case BOTTOM: return Shell32.ABE_BOTTOM;
            default:
                throw new IllegalArgumentException(String.format("unknown edge: %s", e));
        }
    }

    /**
     * Sets the {@link #EDGE edge} property. The edge is validated, even if it is the same
     * as the current edge.
     * @param edge  The new edge that this appbar will occupy.
     * @throws AppBarException reason=AppBarException#ILLEGAL_EDGE if the edge is not allowed
     * by the set of edges.
     * @see #EDGE
     * @see #ALLOWEDEDGES
     * @see #validateEdge
     * @see #onChangedAllowedEdges
     */
    public void setEdge(Edge edge)
    {
        validateEdge(edge);
        Edge oldEdge = this.edge;
        this.edge = edge;
        this.edgeCode = edgeCode(edge);
        if (oldEdge!=edge)
        {
            //transformEdgeDependentData(oldEdge, edge);
            firePropertyChange(EDGE, oldEdge, edge);
            windowPropertyChanged(EDGE);
        }
    }

    private void transformEdgeDependentData(Edge oldEdge, Edge edge)
    {
        //insetsReveal? not sure these need translating, unless the insets
        // are also rotates as the appbar orientation changes
        Sides work = new Sides(false);

        GeomUtil.rotate(autoHideInsets, oldEdge, edge, work);
        GeomUtil.rotate(overrideHideInsets, oldEdge, edge, work);
        
    }

    /**
     * Validates the edge against the set of allowed edges.
     * @param e  The edge to validate.
     * @throws AppBarException with {@code Reason.ILLEGAL_EDGE} if the edge is not allowed.
     * @see #ALLOWEDEDGES
     */
    public void validateEdge(Edge e)
    {
        if (!isEdgeAllowed(e))
            throw AppBarException.Reason.ILLEGAL_EDGE.exception();
    }

    private boolean isEdgeAllowed(Edge e)
    {
        return this.allowedEdges.isSet(e);
    }


    /**
     * Fetches the AppBar edgeCode for the current edge.
     * @return One of {@link Shell32#ABE_LEFT},{@link Shell32#ABE_TOP},
     * {@link Shell32#ABE_RIGHT}, {@link Shell32#ABE_BOTTOM} 
     */
    int getEdgeCode()
    {
        return edgeCode;
    }

    /**
     * Retrieves the {@link #EDGE edge} property.
     * @return  The edge this appbar occupies.
     * @see #EDGE
     */
    public Edge getEdge()
    {
        return edge;
    }

    /**
     * Retrieves the {@link #ALLOWEDEDGES} property value.
     * @return The set of edges that the appbar can occupy.
     */
    public Sides getAllowedEdges()
    {
        return allowedEdges;
    }

    /**
     * Retrieves the {@link #BARSIZE barSize} property.
     * @return The barsize.
     */
    public int getBarSize()
    {
        return barSize;
    }

    public int getDesiredBarSize()
    {
        return desiredBarSize;
    }

    /**
     * Sets the desired bar size {@link #DESIREDBARSIZE }property}.
     * This value is not modified when the appbar is resized.
     * Also sets the {@link #BARSIZE barSize} property, causing the bar to resize
     * to the desired size.
     * @param barSize   The new desired size for this bar.
     */
    public void setDesiredBarSize(int barSize)
    {
        int oldBarSize = desiredBarSize;
        if (oldBarSize!=barSize)
        {
            firePropertyChange(DESIREDBARSIZE, oldBarSize, barSize);
            setBarSize(barSize);
        }
    }

    public void setAutoHideAmount(double amount)
    {
        this.autoHideAmount = amount;
    }

    public double getAutoHideAmount()
    {
        return autoHideAmount;
    }

    /**
     * Attempts to make the appBar size to be the desired size. The appBar may not be the desired size:
     * <ul><li>the size is {@linkplain #clipBarSize(int) clipped} if it is out of bounds,</li>
     * <li>The system cannot allocate the desired space</li>.
     * </ul>
     * Callers can determine the acutal appbar size via {@link #getBarSize()}
     */
    public void forceToDesiredSize()
    {
        setBarSize(clipBarSize(getDesiredBarSize()));
    }

    /**
     * Sets the {@link #BARSIZE barSize} property.
     * @param barSize   The new bar size.
     * @throws AppBarException with the reason {@link AppBarException.Reason#ILLEGAL_BAR_SIZE}
     *  if {@code barSize} is negative or greater than the maximum bar size.
     * @see #getMaximumBarSize
     * @see #clipBarSize(int)
     */
    public void setBarSize(int barSize)
    {
        if (barSize<0 || barSize>getMaximumBarSize(edge))
            throw new AppBarException(AppBarException.Reason.ILLEGAL_BAR_SIZE);
        int old = this.barSize;
        this.barSize = barSize;
        if (barSize!=old)
        {
            firePropertyChange(BARSIZE, old, barSize);
            windowPropertyChanged(BARSIZE);
        }
    }

    /**
     * Ensures that the barsize is within the valid range.
     * @param size  The size to clip.
     * @return the size, such that {@code 0 <= size <= maximumBarSize}.
     */
    public int clipBarSize(int size)
    {
        if (size<0)
            size = 0;
        int max = getMaximumBarSize(edge);
        if (size>max)
            size = max;
        return size;
    }

    /**
     * Determines the maximum size of the appbar.
     * @return
     */
    public int getMaximumBarSize(Edge edge)
    {
        Rectangle screenBounds = getVirtualScreenBounds();
        Orientation o = edge.getOrientation();
        int totalSize = GeomUtil.getSize(screenBounds, o);
        int maxSize = totalSize/2;
        // this kind of assumes this is the only appbar on a given edge.
        // if we have say 4 appbars, each one cannot get half the available space
        return maxSize;
    }

    /**
     * Retrieves the {@link #OVERRIDEHIDEINSETS} property value.
     * @return The overriding hide state for each edge of the appbar.
     */
    public Sides getOverrideHideInsets()
    {
        return overrideHideInsets;
    }

    /**
     * Retrieves the {@link #AUTOHIDEINSETS autoHideInsets} property value.
     * @return For each side, a flag to indicate if the auto-hide state
     * override state is used to set the final hidden state on an inset.
     */
    public Sides getAutoHideInsets()
    {
        return autoHideInsets;
    }

    /**
     * Fetches the insets that are auto-hidden.
     * @return
     */
    public Sides getAutoHiddenInsets()
    {
        return readOnlySides(autoHiddenInsets);
    }

    /**
     * Retrieves the value of the {@link #INSETSHIDESTATE}}
     * @return Returns the actual hidden state of the appbar insets.
     */
    public Sides getInsetsHideState()
    {
        return readOnlySides(insetsHideState);
    }

    /**
     * Returns the value of sides that is read only. (Actually, the current implementation
     * returns a new clone of the actual instance.)
     * @param sides
     * @return A read-only or independent copy of the Sides instance.
     */
    protected Sides readOnlySides(Sides sides)
    {
        return new Sides(false).assign(sides);
    }

    /**
     * Determines if the inset on an edge of the appbar is hidden. This is determined
     * by the {@link #AUTOHIDEINSETS autoHideInsets} setting for te edge,
     * which selects between the {@link #AUTOHIDDENINSETS autoHiddenInsets} and
     * {@link #OVERRIDEHIDEINSETS overrideHideInsets}.
     * @param e The edge to test.
     * @return  {@code true} if the inset is hidden.
     */
    public boolean isInsetHidden(Edge e)
    {
        return autoHideInsets.isSet(e) ?
                autoHiddenInsets.isSet(e) :
                overrideHideInsets.isSet(e);
    }

   /**
     * Method called when a property that affects the appbar window has changed.
     * @param property  The property that was changed. May be {@code null} if several properties
     * were changed.
     */
    protected void windowPropertyChanged(String property)
    {
        if (isUpdateWindow())
            setPositionFromModel();
    }

    /**
     * Registers the window, and which is optionally automatically
     * unregistered when closed.
     * @param window            The window to register.
     * @param autoUnregister    If {@code true}, the window will automatically be unregistered
     *  when the window is closed.
     *
     * @see #register(java.awt.Window)
     */
    public void register(Window window, boolean autoUnregister)
    {
        register(window);
        if (autoUnregister)
        {
            autoUnregisterListener = new Unregister();
            window.addWindowListener(autoUnregisterListener);
        }
    }

    /**
     * Associates this appbar with a window.
     *
     * @param window    The window to associate with this appbar.
     * @throws AppBarException reason {@link AppBarException.Reason#APPBAR_ALREADY_REGISTERED}
     *      if the appbar is already associated with a window.
     *  reason {@link jdesktop.appbar.AppBarException.Reason#APPBAR_NO_NATIVE_WINDOW} if the window does not
     * have a native peer.
     *
     * @see #REGISTERED
     * @see #getWindow()
     * @see #unregister()
     */
    public void register(Window window)
    {
        // todo - save restored/maximized state
        if (this.window != null)
            throw new IllegalStateException("AppBar already registered with a window");

        Pointer hWnd = Native.getWindowPointer(window);
        if (hWnd == null)
            throw new IllegalArgumentException("cannot retrieve native window handle");

        this.hWnd = hWnd;
        this.window = window;

        originalBounds = window.getBounds();
        originalVisible = window.isVisible();
        originalStyle.assignFromWindow(window, hWnd);

        appBarStyle.assign(originalStyle);
        appBarStyle.modifyStyleSet(WindowStyle.StyleGroup.EXTENDED, appBarExStyleFlagsAdd, 0);
        appBarStyle.modifyStyleSet(WindowStyle.StyleGroup.ORIGINAL, appBarStyleFlagsAdd, appBarStyleFlagsRemove);

        wndProc = AppBarWndProc.install(this, appBarEventsHandler);

        setPositionFromModel();
    }

    /**
     * Retrieves the window client area in screen co-ordinates.
     * @return
     */
    public Rectangle getWindowClientArea()
    {
        return getWindowClientArea(new Rectangle());
    }

    /**
     * Fetches the client rectangle of the window in screen co-ordinates.
     * @param r The rectangle to recieve the client co-ordinates.
     * @return  {@code r}
     */
    public Rectangle getWindowClientArea(Rectangle r)
    {
        user32.GetClientRect(hWnd, appBarData.rc);
        appBarData.rc.copyTo(r);
        User32.Point point = new User32.Point();
        user32.ClientToScreen(hWnd, point);
        r.x += point.x;
        r.y += point.y;
        return r;
    }

    @Override
    public AppBarModel getAppBarModel()
    {
        return this;
    }

    @Override
    public Rectangle getReservedArea(Rectangle rect)
    {
        if (isAreaReserved())
        {
            Rectangle rc = lastSetArea;
            rect.setBounds(rc);
        }
        else
        {
            rect.setBounds(0,0,0,0);
        }
        return rect;
    }

    public void setDragGuide(DragGuide guide)
    {
        this.dragGuide = guide;
    }


    public DragGuide getDragGuide()
    {
        if (dragGuide==null)
            setDragGuide(initialDragGuide());
        return dragGuide;
    }

    protected DragGuide initialDragGuide()
    {
        return new DragGuide();
    }

    public void setFloatingSize(int width, int height)
    {
        floatingSize.width = width;
        floatingSize.height = height;
    }

    /**
     * Window listener that unregisters the appbar when the window is closed.
     */
    class Unregister extends WindowAdapter
    {
        @Override
        public void windowClosing(WindowEvent e)
        {
            unregister();
        }
    }    


    /**
     * Removes the changes applied to the Window. Restores the window
     * to the original position and visibility state.
     * If a window has not been associated with this appbar,
     * this method silently returns.
     *
     * @see #register
     */
    public void unregister()
    {
        if (window == null)
            return;

        final boolean restore = isRestoreWindowState();

        // make invisible if that's how it will be so, that changes in position are not apparent
        // just before the window disappears
        if (restore && !originalVisible)
            window.setVisible(false);

        if (autoUnregisterListener != null)
        {
            window.removeWindowListener(autoUnregisterListener);
            autoUnregisterListener = null;
        }
        if (wndProc != null)
        {
            wndProc.uninstall();
            wndProc = null;
        }
        appBarUnregister();
        if (restore)
            originalStyle.assignToWindow(window, hWnd);
        if (restore && originalBounds != null)
        {
            window.setBounds(originalBounds);
            originalBounds = null;
        }

        user32.SetWindowRgn(hWnd, null, false);
        lastRegion = null;

        if (restore && originalVisible)
            window.setVisible(true);

        window = null;
        hWnd = null;
    }

    /**
     * Determines if a window is registered with this appbar.
     * @return
     */
    public boolean isRegistered()
    {
        return window != null;
    }

    /**
     * Unregisters the appbar with the Shell by sending a {@code ABM_REMOVE} message.
     * The shell frees any reserved screen area.
     */
    protected void appBarUnregister()
    {
        // if space allocated, the appbar must be registered
        assert(!spaceAllocated || appBarRegistered);
        if (!appBarRegistered)
            return;

        AppBarData data = appBarData;
        data.hWnd = hWnd;
        appBarMessage(Shell32.ABM_REMOVE, data);
        // return value ignored - docs say always returns true
        spaceAllocated = false;
        appBarRegistered = false;

        firePropertyChange(RESERVEDAREA, lastSetArea, null);
    }

    /**
     * Registers the appbar with the shell by sending a {@code ABM_NEW} message.
     * No space is allocated.
     */
    protected void appBarRegister()
    {
        if (appBarRegistered)
            return;
        AppBarData data = appBarData;
        data.hWnd = hWnd;
        data.uCallbackMessage = DockedWndProc.notifyAppBarMsg;
        int ret = appBarMessage(Shell32.ABM_NEW, data);
        if (ret <= 0)
            throw new AppBarException(String.format("Could not register appbar: return code %1$d (0x%1$x)", ret));
        appBarRegistered = true;
        assert(!spaceAllocated);
    }

    // todo - ensure this is on the AWT event thread. 
    private void onStyleChanged()
    {
        WindowStyle.updateWindow(window, hWnd, true);
        window.validate();
        window.repaint();
    }

    /**
     * Determines if the appBar has been registered.
     * @return  {@code true} if the appBar has been registered.
     *
     * Note that registration does not imply that space has been reserved for the appbar.
     * @see #register(java.awt.Window, boolean)
     * @see #isAreaReserved()
     */
    public boolean isAppBarRegistered()
    {
        return appBarRegistered;
    } // registration

    /**
     * Queries the bounds of the available position for the app bar along the requested edge.
     * Typically the bounds are the bounds of the window.
     *
     * @param bounds The bounds to query for placement of the app bar.
     * @param target the target rectangle to recieve the possibly modified bounds.
     * @see #setEdge(Edge)
     */
    protected void queryPosition(Rectangle bounds, Rectangle target)
    {
        doPositionMessage(Shell32.ABM_QUERYPOS, bounds, target);
    }

    /**
     * Sets the allocation position for this app bar. Note that the bar window is not resized, but merely
     * the space is allocated for it. On return, the target rectangle holds the actual space allocated.
     *
     * @param bounds    The bounds of the appbar window. (Includes non-client area.)
     * @param target    Receives the actual set bounds of the window.
     */
    protected void setPosition(Rectangle bounds, Rectangle target)
    {
        assert(isUpdateReservedArea());
        if (log.isLoggable(Level.FINEST))
            log.finest(String.format("requesting abbBar bounds %s, spaceAllocated=%b", bounds, isAreaReserved()));
        doPositionMessage(Shell32.ABM_SETPOS, bounds, target);
        spaceAllocated = true;
        lastSetArea.setBounds(target);
        if (log.isLoggable(Level.FINEST))
            log.finest(String.format("granted abbBar bounds %s", target));
    }

    /**
     * Handles the {@code }SET_POS and {@code QUERY_POS} messages.
     *
     * @param abmMsg    The message to send to the shell.
     * @param bounds    The bounds of the appbar to query or set.
     * @param target    Recieves the acutal bounds allocated to the appbar. Must not be null,
     * but can be the same reference as {@code bounds}.
     */
    private void doPositionMessage(int abmMsg, Rectangle bounds, Rectangle target)
    {
        AppBarData data = appBarData;
        data.hWnd = hWnd;
        data.uEdge = edgeCode;
        data.rc.setBounds(bounds);
        appBarMessage(abmMsg, data);
        data.rc.copyTo(target);
    }

    /**
     * Sends an app bar message to the shell.
     *
     * @param abmMsgID The message ID
     * @param data     The APPBARDATA struct.
     * @return A message-dependent value.
     * @see <a href="http://msdn.microsoft.com/en-us/library/bb762108%28VS.85%29.aspx">SHAppBarMessage on MSDN</a>
     */
    protected int appBarMessage(int abmMsgID, AppBarData data)
    {
        return shell32.SHAppBarMessage(abmMsgID, data);
    } // low level positioning

    /**
     * Sets the appbar reserved area to correspond to the current window rectangle. This method is
     * called when the window has changed size. The window position is assumed to be correct,
     * e.g. occupies the full available length of the edge.
     * <p>
     * The Win32 getWindowRect is used rather than Window.getBounds -
     * the AWT window bounds may be out of sync (to be updated later).
     * (The Windows event pump and the AWT event queue are not the same thread.)
     * <p>
     * This method uses {@link #setPosition(java.awt.Rectangle, java.awt.Rectangle)}
     * directly, and avoids the recomputation in
     *
     */
    @Override
    protected Rectangle setPositionFromWindow()
    {
        Rectangle r = new Rectangle();
        appBarRectFromWindowRect(r);

        if (isUpdateReservedArea())
            setPosition(r, r);
        else
            queryPosition(r, r);
        return r;
    }

    /**
     * Determines the appbar bounds from the window bounds.
     * If no insets are hidden, then the two are identical, otherwise, the window rectangle is enlarged
     * and offset to compensate for hidden insets (and partial reveal of hidden insets.)
     * @param r     A work rectangle. Used to retrieve the window rectangle.
     * @return      The appbar rectangle that should be reserved to display the appbar at the current
     * window size.
     */
    Rectangle appBarRectFromWindowRect(Rectangle r)
    {
        user32.GetWindowRect(hWnd, appBarData.rc);
        appBarData.rc.copyTo(r);
        // need to remove the insets, as these are not part of the allocated space
        Insets revealInsets = getInsetsReveal(getInsetsOriginal(), insetsReveal, new Insets(0,0,0,0));
        addInsets(r, revealInsets, insetsHideState, -1);
        return r;
    }

    /**
     * Recomputes the barSize from the window rect.
     * @see #setPositionFromModel()
     */
    public void setBarSizeFromWindow()
    {
        user32.GetClientRect(hWnd, appBarData.rc);
        Rectangle r = new Rectangle();
        appBarData.rc.copyTo(r);

        int size = GeomUtil.getSize(r, edge.getOrientation());
        int oldSize = this.getBarSize();
        setBarSize(clipBarSize(size));
        if (log.isLoggable(FINER))
            log.log(FINER, String.format("setBarSizeFromWindow: oldSize=%d, size=%d, clientRect=%s",oldSize, size, r));

        // force an update even though the property hasn't changed.
        if (size==oldSize)
            setPositionFromModel();
    } // size to/from window

    @Override
    public void setPositionFromModel()
    {
        if (!isRegistered())
            return;
        
        if (isDocked())
        {
            /**
             * If space has not been allocated with the shell,
             * reserves the appbar area with the shell and sizes the window.
             * On calling this method, the appbar area can be in one of these states:
             * <ol>
             * <li>unregistered, window is {@code }null}: silently returns</li>
             * <li>{@code !}{@link #isAppBarRegistered}: registers the appbar with the shell.</li>
             * <li>{@code !}{@link #isAreaReserved()}: positions the appbar using {@link #setPositionFromModel()}</li>
             * </ol>
             * Thus, on return from this method, the appbar area is reserved and the appbar positioned
             * according to the position properties.
             */
            if (!isAppBarRegistered())
            {
                appBarRegister();
                appBarStyle.assignToWindow(window, hWnd);
                onStyleChanged();
            }

            setDockedPositionFromModel();
            assert isAreaReserved();
        }
        else
        {
            if (isUpdateReservedArea())
                appBarUnregister();
            originalStyle.assignToWindow(window, hWnd);
            onStyleChanged();
        }
        
    }

    /**
     * Positions the app bar according to the current screen size and
     * these layout properties: {@link #EDGE edge}, {@link #BARSIZE barSize}, and the
     * hidden insets properites {@link #OVERRIDEHIDEINSETS}, {@link #AUTOHIDEINSETS}.
     * This method is called to synchronize the appBar area and the window size with
     * the UI properties. Note that if {@link #isUpdateReservedArea()} is {@code false}
     * no changes to the reserved desktop are are made, but the window position is altered to
     * the place where the appbar would appear if the area were reserved. This is useful for
     * dragging without needing to reposition all the other windows on the desktop until the
     * drag is complete.
     *
     * @see #computeAppBarWindowBounds
     * @see #setBarSizeFromWindow()
     * @see #setPositionFromWindow()
     */
    protected void setDockedPositionFromModel()
    {
        assert(isDocked());
        if (!isDocked())
            return;
        
        Rectangle windowBounds = new Rectangle();
        Rectangle appBarBounds = new Rectangle();
        Rectangle screenBounds = getVirtualScreenBounds();

        Insets insets = computeAppBarWindowBounds(windowBounds, appBarBounds,
                screenBounds, isUpdateReservedArea());

        if (log.isLoggable(FINE))
            log.log(FINE, String.format("setting window size %s", windowBounds));

        clipWindowInsets(screenBounds, windowBounds, appBarBounds, insets, insetsHideState);

        user32.SetWindowPos(hWnd, null,
                windowBounds.x,  windowBounds.y, windowBounds.width, windowBounds.height,
                User32.SWP_NOACTIVATE|User32.SWP_NOZORDER|User32.SWP_SHOWWINDOW);
        user32.UpdateWindow(hWnd);
    }

    protected Insets computeAppBarWindowBounds(Rectangle windowBounds, Rectangle appBarBounds, Rectangle screenBounds)
    {
        return computeAppBarWindowBounds(windowBounds, appBarBounds, screenBounds, isUpdateReservedArea());
    }

   /**
     * Computes the appBar and Window bounds and optionally updates the appbar area.
     * @param windowBounds  On entry, not defined. On exit, holds the window bounds in screen co-ordinates.
     * @param appBarBounds  On entry, not defined. ON exit, holds the appBar bounds in screen co-ordinates.
     * @param screenBounds  The bounds of the screen. These are used to set the extent of the appbar.
     * @param updateAppBarArea  Flag to update the reserved area. When {@code true}, the reserved
     * area is updated to the computed area.
     *
     * @return
     *
     * On return, the {@link #insetsHideState} are set to the insets that are added to the appbar
     * bounds to get the window bounds.
     *
     * todo - should use a strategy for query/set appbar area so this can be tested independently of the shell.
     */
    protected Insets computeAppBarWindowBounds(Rectangle windowBounds, Rectangle appBarBounds, Rectangle screenBounds,
                                               boolean updateAppBarArea)
    {
        // the full insets - used by the computeBounds method
        Insets fullInsets = getInsetsOriginal();
        // the partial insets, essentially what is to be considered the real insets.
        Insets insets = getInsetsReveal(fullInsets, insetsReveal, new Insets(0,0,0,0));
        Sides requestedHidden = insetsHideState;
        Sides actualHidden = new Sides(false);

        // the default query is for a rectangle with 3 sizes adjacent to the screen.
        // assume 3 sides are up against the edge of the screen, and just the front
        // is not.
        autoHiddenInsets.setAll(true);
        autoHiddenInsets.set(edge.getOppositeEdge(), false);
        updateInsetsHiddenState(actualHidden);
        // todo - log hidden insets state
        do
        {
            requestedHidden.assign(actualHidden);

            // compute the area we need to reserve. Hidden insets do not need to be included
            // as part of this area. Visible insets are included.
            computeBounds(windowBounds, screenBounds, getEdge(), getBarSize(), fullInsets, insets, requestedHidden);

            if (log.isLoggable(FINE))
                log.log(FINE, String.format("requested appbar size %s", windowBounds));

            // use query first to avoid lots of screen shuffling if we don't get what we ask for.
            // despite the names, the first is the desired bounds, the second, the actual bounds.
            queryPosition(windowBounds, appBarBounds);

            if (updateAppBarArea && windowBounds.equals(appBarBounds))
            {
                if (log.isLoggable(FINE))
                    log.log(FINE, String.format("granted same appbar bounds, setting appbar position %s", appBarBounds));

                setPosition(appBarBounds, appBarBounds);
            }

            // setPosition above can change the bounds (race condition)
            if (!windowBounds.equals(appBarBounds))
            {
                log.log(FINE, String.format("granted different appbar bounds, %s", appBarBounds));

                // if the width of the bar is smaller, try to set it to the width desired.
                Extent desired = GeomUtil.getExtent(windowBounds, edge.getOrientation());
                Extent actual = GeomUtil.getExtent(appBarBounds, edge.getOrientation());

                // adjust the appbar size/placement - this may change the hidden insets state
                if (desired.getSize()!=actual.getSize())
                {
                    GeomUtil.sizeExtent(actual, edge.isOrigin(), desired.getSize());
                    GeomUtil.setExtent(appBarBounds, edge.getOrientation(), actual);
                    queryPosition(appBarBounds, appBarBounds);
                    log.log(FINE, String.format("granted appbar bounds, %s", appBarBounds));
                }

                determineAutoHiddenInsets(autoHiddenInsets, screenBounds, appBarBounds);
                updateInsetsHiddenState(actualHidden);

                // we can set the position because
                if (updateAppBarArea && actualHidden.equals(requestedHidden))
                {
                    setPosition(appBarBounds, appBarBounds);
                    determineAutoHiddenInsets(autoHiddenInsets, screenBounds, appBarBounds);
                    updateInsetsHiddenState(actualHidden);
                }

            }
        }
        while (!actualHidden.equals(requestedHidden));

        // make desiredBounds be the appBar rect
        // actualBounds are the windowRect
        windowBounds.setBounds(appBarBounds);

        // add the hidden insets to the window
        addInsets(windowBounds, insets, insetsHideState, 1);
        return insets;
    }

    /**
     * Fetches the insets (non-client area) of the window.
     * @return An {@code Insets} instance filled with the insets for each side.
     */
    protected Insets getInsetsOriginal()
    {
        return window.getInsets();
    }

    /**
     * Fetches the size of the insets when they are hidden.
     * @return
     */
    protected static Insets getInsetsReveal(Insets original, Insets reveal, Insets target)
    {
        target.set(revealInset(original.top, reveal.top),
                   revealInset(original.left, reveal.left),
                revealInset(original.bottom, reveal.bottom),
                revealInset(original.right, reveal.right)
                );
        return target;
    }

    /**
     * Computes the final size of an inset from the original inset size and the
     * reveal quantity. A positive {@code reveal} shows the same number of pixels from the inside
     * edge of the inset. A negative {@code reveal} shows the inset, stopping {@code abs(reveal)} pixels
     * from the outer edge.
     * @param inset The original inset
     * @param reveal    The reveal value
     * @return  The new size of the inset.
     */
    protected static int revealInset(int inset, int reveal)
    {
        int value;
        if (reveal==0)
            value = inset;
        else if (reveal<0)
        {
            if (reveal<-inset)
                reveal = -inset;
            value = -reveal;
        }
        else
        {
            if (reveal>inset)
                reveal = inset;
            value = inset-reveal;
        }
        return value;
    }

    /**
     * Determines which insets will be auto-hidden, if auto-hide is eanbled for that inset edge.
     * An inset is auto-hidden if it is adjacent to the edge of the screen.
     * @param result    The sides of insets that will be auto-hidden.
     * @param screen    The screen bounds.
     * @param appBar    The appBar bounds.
     */
    private static void determineAutoHiddenInsets(Sides result, Rectangle screen, Rectangle appBar)
    {
        result.setLeft(screen.x==appBar.x);
        result.setTop(screen.y==appBar.y);
        result.setRight(screen.x+screen.width==appBar.x+appBar.width);
        result.setBottom(screen.y+screen.height==appBar.y+appBar.height);
    }

    /**
     * Computes the final hide inset state.
     * @param state The target instance to recieve the hide state for each inset.
     * @see #isInsetHidden(Edge)
     */
    private void updateInsetsHiddenState(Sides state)
    {
        boolean old = state.setFreezeNotification(true);
        for (Edge e:Edge.values())
        {
            state.set(e, isInsetHidden(e));
        }
        state.setFreezeNotification(old);
    }

    /**
     * @param screenBounds
     * @param windowRect
     * @param insets
     * @param hiddenInsets
     * NB: Modofies apPBarRect to return the applied clip region.
     */
    private void clipWindowInsets(Rectangle screenBounds, Rectangle windowRect, Rectangle appBarRect, Insets insets, Sides hiddenInsets)
    {
        boolean redraw = false;
        if (regionClipEnabled && !hiddenInsets.areAllSetTo(false))
        {
            // compute the region relative to window co-ordinates
            // get appRect in window co-ordinates
            Rectangle r = appBarRect;
            r.x -= windowRect.x;
            r.y -= windowRect.y;

            // if the edge that is not adjacent to the edges of the screen is draggable, then do not
            // clip that side.
            Edge opposite = edge.getOppositeEdge();
            if (isDraggable(opposite))
            {
                switch (edge)
                {
                    case LEFT:
                        r.width = screenBounds.width-r.x;
                        break;
                   case RIGHT:
                        r.width += r.x-screenBounds.x;
                        r.x = screenBounds.x;
                        break;
                   case TOP:
                       r.height = screenBounds.height-r.y;
                       break;
                   case BOTTOM:
                       r.height += r.y-screenBounds.y;
                       r.y = screenBounds.y;
                       break;
                }
            }

            if (lastRegion==null || !lastRegion.equals(r))
            {
                lastRegion = r;
                if (log.isLoggable(FINER))
                    log.finer(String.format("setting window clip region %s", r));
                Pointer rgn = GDI32.INSTANCE.CreateRectRgn(r.x, r.y, r.x+r.width, r.y+r.height);
                // last param is false - no need to redraw since we are
                user32.SetWindowRgn(hWnd, rgn, redraw);
            }
        }
        else
        {
            // clear any clip region
            if (log.isLoggable(FINER))
                log.finer(String.format("clearing window clip region (wasSet=%b)",lastRegion!=null));
            if (lastRegion!=null)
            {
                lastRegion = null;
                user32.SetWindowRgn(hWnd, null, redraw);
            }
        }
    }

    // will be extended....maybe.
    private boolean isDraggable(Edge edge)
    {
        return edge==this.edge.getOppositeEdge();
    }

    /**
     * Converts between the appBar bounds and windowBounds.
     * Increases the rectangle by the size of hidden insets multiplied by a factor.
     * Visible insets are part of the appbar area, hidden insets are not.
     *
     * @param r         The rectangle to deflate.
     * @param insets    The size of the insets.
     * @param hidden    The insets that are hidden. A side is set to {@code true} if the inset
     * on that side is hidden (not part of the appbar area.)
     * @param factor    The multiplication factor for each side. Set to {@code 1} to add the insets,
     * set to {@code -1} to subtract them.
     */
    private static void addInsets(Rectangle r, Insets insets, Sides hidden, int factor)
    {
        if (hidden.isLeft())
        {
            r.x -= insets.left*factor;
            r.width += insets.left*factor;
        }
        if (hidden.isTop())
        {
            r.y -= insets.top*factor;
            r.height += insets.top*factor;
        }
        if (hidden.isRight())
            r.width += insets.right*factor;
        if (hidden.isBottom())
            r.height += +insets.bottom*factor;
    }


    private static boolean setInsetIfHidden(Orientation orientation, boolean atOrigin, Insets insets, Sides hidden, Insets insetsOut)
    {
        boolean hiddenInset = hidden.getValueAt(orientation, atOrigin);
        if (hiddenInset)
        {
            int value = GeomUtil.getInset(insets, orientation, atOrigin);
            GeomUtil.setInset(insetsOut, orientation, atOrigin, value);
        }
        return hiddenInset;
    } // AppBar sizing/positioning, insets management


    /**
     * Retrieves the total size of the screen.
     * @return The total size of all monitors.
     * // todo - move elsewhere. Not specific to appbar.
     */
    public Rectangle getVirtualScreenBounds()
    {
        // todo - compute total size from screen GraphicsDevices, or use native code...
        // we use bounds rather than size, because the top-left can apparently be negative.
        // Not sure this is the case on windows.
        return new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
    }

    @Override
    public Window getWindow()
    {
        return window;
    }

    public Unregister getAutoUnregisterListener()
    {
        return autoUnregisterListener;
    }

    public Pointer getWindowHandle()
    {
        return hWnd; 
    }


    public void onSizingOrMovingEnter()
    {
        setUpdateReservedArea(false);
        setUpdateWindow(false);
    }

    public void onSizingOrMovingExit()
    {
        setUpdateReservedArea(true);
        setUpdateWindow(true);
        if (isUpdateReservedArea())
        {
            setPositionFromWindow();
            setBarSizeFromWindow();
            setPositionFromModel();
        }
    }

    /**
     * Notification on the windows message pump that the display has changed resolution.
     * The {@link #BARSIZE barSize} is clipped to ensure it is still within acceptable bounds.
     * @see #clipBarSize(int)
     */
    public void onDisplayChange()
    {
        if (!isDocked())
            return;

        int old = getBarSize();
        setBarSize(clipBarSize(getDesiredBarSize()));
        windowPropertyChanged(null);
    }

    /**
     * Notification from the windows message pump
     * of a position and/or size change to the window.
     * If space is allocated, the appbar is resized to fit the new window bounds.
     *
     */
    public void onBoundsChanged()
    {
        if (!isDocked())
            return;

        if (spaceAllocated && isResizable())
            setBarSizeFromWindow();
    }

    /**
     * Notification that
     * window is being resized by the user dragging.
     * The appbar is updated directly to correspond to the window size.
     * Note that this method is expecting that the window is correctly sized.
     */
    public void onResizing()
    {
        setPositionFromWindow();
    }

    /**
     * Notification that the window is activated.
     * If the appbar space has been allocated, sends a
     * {@link Shell32#ABM_ACTIVATE} message to the appbar.
     */
    public void onActivate()
    {
        if (!spaceAllocated)
            return;

        if (log.isLoggable(FINE))
            log.log(FINE, "Activating appBar");
        AppBarData data = appBarData;
        data.hWnd = hWnd;
        data.lParam = 0;
        appBarMessage(Shell32.ABM_ACTIVATE, data);
    }

    /**
     * Notification that the size or position has changed. Sends the
     * {@link Shell32#ABM_WINDOWPOSCHANGED} notification to the appbar if the
     * appbar is bound to the window.
     */
    public void onWindowPosChanged()
    {
        if (!spaceAllocated)
            return;

        if (log.isLoggable(FINE))
            log.log(FINE, "notifying appBar windowPosChanged");

        AppBarData data = appBarData;
        data.hWnd = hWnd;
        appBarMessage(Shell32.ABM_WINDOWPOSCHANGED, data);
    }


    /**
     * Retrieves the value of the {@link #RESTORESTATE restoreState} property.
     * @return The restoreState property.
     */
    public boolean isRestoreWindowState()
    {
        return restoreWindowState;
    }

    /**
     * Sets the value of the {@link #RESTORESTATE restoreState} property.
     * @param restoreWindowState  The new value of the restore state property.
     */
    public void setRestoreWindowState(boolean restoreWindowState)
    {
        this.restoreWindowState = restoreWindowState;
    }

    /**
     * Determines if space has been allocated for the appbar. When {@code true}, the reserved size
     * and the window size are bound. The reserved area is available via {@link #getReservedArea(java.awt.Rectangle)}.
     * When {@code false} changes to the window size do not affect the reserved area, which remains empty.
     * @return The status of space allocated.
     */
    @Override
    public boolean isAreaReserved()
    {
        return spaceAllocated;
    }

    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
        propertyChangeSupport.removePropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
    {
        propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
    {
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    protected void firePropertyChange(String property, Object oldValue, Object newValue)
    {
        propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
    }

    /**
     * Pauses/resumes property change notifications, and postpones updating the UI when
     * properties change. 
     * @param freezeNotification {@code true} to enable immediate property change notification,
     * {@code cfalse} to queue notification events.
     * @return  The previous value. This can be used to save/restore the original state, allowing
     * nested calls to this method.
     */
    public boolean setFreezeNotification(boolean freezeNotification)
    {
        return propertyChangeSupport.setFreezeNotification(freezeNotification);
    }

    public void setFloatingLocation(int x, int y)
    {
        floatingLocation.x = x;
        floatingLocation.y = y;
    }

    /**
     * Determines if changes to the window bounds also changes the reserved appbar area.
     * Normally, this is {@code true}, but when performing a sequence of size changes,
     * such as when dragging or sizing a window, then it can improve performance
     * to set this to {@code false}.
     * @return
     */
    @Override
    public boolean isUpdateReservedArea()
    {
        return updateReservedAreaCount==0;
    }

    @Override
    public void setUpdateReservedArea(boolean updateReservedArea)
    {
        boolean old = isUpdateReservedArea();
        updateReservedAreaCount += updateReservedArea ? -1 : 1;
        boolean newUpdateArea = isUpdateReservedArea();
        // todo - reserve appbar area from window position?
    }

    public Insets getInsetsReveal()
    {
        return insetsReveal;
    }

    public boolean isDocked()
    {
        return docked;
    }

    public void setDocked(boolean docked)
    {
        boolean old = this.docked;
        if (old!=docked)
        {
            this.docked = docked;
            firePropertyChange(DOCKED, old, docked);
            windowPropertyChanged(DOCKED);
        }
    }

    public void setFloating(boolean floating)
    {
        setDocked(!floating);
    }

    public boolean isFloatable()
    {
        return floatable;
    }

    public void setFloatable(boolean floatable)
    {
        if (floatable!=this.floatable)
        {
            this.floatable = floatable;
            firePropertyChange(FLOATABLE, !floatable, floatable);
        }
    }

    public boolean isResizable()
    {
        return resizable;
    }

    public void setResizable(boolean resizable)
    {
        if (resizable!=this.resizable)
        {
            this.resizable = resizable;
            firePropertyChange(RESIZABLE, !resizable, resizable);
        }
    }

    public boolean isDraggable()
    {
        return draggable;
    }

    /**
     * Sets the {@link #DRAGGABLE draggable} state of this appbar.
     * @param draggable
     */
    public void setDraggable(boolean draggable)
    {
        if (this.draggable!=draggable)
        {
            this.draggable = draggable;
            firePropertyChange(DRAGGABLE, !draggable, draggable);
        }
    }

    @Override
    public Rectangle getFloatableDragBounds()
    {
        return floatableRectangle;
    }

    @Override
    public void setFloatableDragBounds(Rectangle floatableRectangle)
    {
        this.floatableRectangle = floatableRectangle;
    }

    /**
     * Overrides the current hit test value for the non client area..
     * @param hit   One of the {@code WM_NCHITTEST} constants, {@code }HT_xxx}
     * @return The desired hittest code.
     *
     * Returning {@link User32#HTCAPTION} for non-caption areas will allow the user
     * to drag the window.
     */
    public int appBarHitTest(int x, int y, int hit)
    {
        if (hit==User32.HTCLIENT)
            hit = User32.HTCAPTION;
        return hit;
    }

    /**
     * Notification that the app bar is being sized. The parameters define the bounds of
     * the sizing band. If the applciation wishes to change the sizing bounds, it can do so
     * by returning the new sizing bounds in a rectangle.
     * @param left
     * @param top
     * @param right
     * @param bottom
     * @return
     */
    public Rectangle appBarSizing(int iEdge, int left, int top, int right, int bottom)
    {
        return null;
    }

    @Override
    public Rectangle getDockedBounds(Rectangle rect)
    {
        rect = GeomUtil.initRect(rect);
        computeAppBarWindowBounds(rect, new Rectangle(), getVirtualScreenBounds(), false);
        return rect;
    }

    @Override
    public Rectangle getFloatingBounds(Rectangle target)
    {
        target = GeomUtil.initRect(target);
        target.x = floatingLocation.x;
        target.y = floatingLocation.y;
        target.width = floatingSize.width;
        target.height = floatingSize.height;        
        return target;
    }

    @Override
    public boolean isUpdateWindow()
    {
        return updateWindow==0;
    }

    @Override
    public void setUpdateWindow(boolean update)
    {
        boolean old = this.isUpdateWindow();
        updateWindow += (update ? -1 : 1);
        boolean newUpdate = isUpdateWindow();
        if (old!=newUpdate)
        {
            firePropertyChange(UPDATE_WINDOW, old, newUpdate);
        }
        setPositionFromModel();
    }


}
